package org.jflame.auth;

import org.jflame.auth.authc.AuthcException;
import org.jflame.auth.authc.AuthcException.AuthcError;
import org.jflame.auth.authc.FailedLoginHandler;
import org.jflame.auth.authc.OkLoginHandler;
import org.jflame.auth.authc.SimpleAuthcInfo;
import org.jflame.auth.authc.SimpleLoginUser;
import org.jflame.auth.authc.assist.FailureCounter;
import org.jflame.auth.authc.token.AuthToken;
import org.jflame.auth.authz.SimpleRole;
import org.jflame.auth.context.UserContext;
import org.jflame.auth.context.UserContextHolder;
import org.jflame.auth.session.ConcurrentSessionControlService;
import org.jflame.auth.session.SessionAdapter;
import org.jflame.commons.util.CollectionHelper;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import javax.security.auth.login.LoginException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LoginContext {

    protected final Logger logger = LoggerFactory.getLogger(getClass());

    protected final AuthService authService;
    protected final SessionAdapter sessionAdapter;
    protected FailureCounter failedLoginCounter;

    private ConcurrentSessionControlService concurrentSessionControl;

    private List<FailedLoginHandler> failedLoginHandlers = new ArrayList<>();
    private List<OkLoginHandler> okLoginHandlers = new ArrayList<>();

    private boolean allowWithoutRole = false;// 允许无角色用户登录
    private boolean hideAccountNotFoundException = true;// 隐藏账号不存在的异常信息,避免用户名猜测

    public LoginContext(AuthService authService, SessionAdapter sessionAdapter) {
        this.authService = authService;
        this.sessionAdapter = sessionAdapter;
    }

    /**
     * 登录操作
     * 
     * @param token 输入的身份token
     * @return
     * @throws AuthcException
     */
    public LoginUser login(AuthToken token) throws AuthcException {

        if (failedLoginCounter != null && failedLoginCounter.isLimited(token.stringPrincipal())) {
            throw new AuthcException(AuthcError.AccountLocked);
        }
        // 认证
        SimpleAuthcInfo user = null;
        try {
            user = authService.authenticate(token);
        } catch (Throwable e) {
            logger.error("登录验证异常,参数:" + token, e);
            AuthcException ae;
            if (e instanceof AuthcException) {
                ae = (AuthcException) e;
                if (hideAccountNotFoundException && ae.isError(AuthcError.AccountNotFound)) {
                    throw new AuthcException("用户名或密码不正确");
                }
            } else {
                ae = new AuthcException(e);
            }
            onFailedLogin(token, ae);
            throw ae;
        }

        // 授权
        Set<? extends SimpleRole> userRoles = authService.getRoles(user);
        if (!allowWithoutRole && CollectionHelper.isEmpty(userRoles)) {
            throw new AuthcException("用户未分配任何角色,不允许登录");
        }

        // 构造一个通用的登录用户对象
        LoginUser loginUser = createLoginUser(user, userRoles);
        // 保存会话
        saveSession(loginUser, sessionAdapter);
        onSuccessfulLogin(loginUser);

        return loginUser;
    }

    /**
     * 认证通过后保存会话
     * 
     * @param loginUser
     * @param sessionOperation
     * @throws LoginException
     */
    protected void saveSession(final LoginUser loginUser, final SessionAdapter sessionOperation) throws AuthcException {
        // 判断是否限制登录并发会话数量
        if (concurrentSessionControl != null) {
            concurrentSessionControl.onAuthentication(loginUser, sessionOperation);
        }
        // 注销旧会话,生成新会话避免固定会话欺骗攻击
        sessionAdapter.renew();
        // 验证成功,保存会话
        UserContext ctx = UserContextHolder.getContext();
        ctx.setUser(loginUser);
        sessionAdapter.setAttribute(UserContext.CONTEXT_KEY, ctx);
        // 登记新的账号会话
        if (concurrentSessionControl != null) {
            concurrentSessionControl.getSessionRegistry()
                    .registerNewSession(sessionAdapter.getSessionId(false)
                            .get(),
                            ctx.getUser()
                                    .getUserName());
        }

    }

    void onFailedLogin(final AuthToken token, final AuthcException ae) {
        for (FailedLoginHandler handler : failedLoginHandlers) {
            handler.onFailure(token, ae);
        }
    }

    void onSuccessfulLogin(final LoginUser loginUser) {
        for (OkLoginHandler handler : okLoginHandlers) {
            handler.onSuccessful(loginUser);
        }
    }

    public final void setFailedLoginCounter(FailureCounter failedLoginCounter) {
        this.failedLoginCounter = failedLoginCounter;
        if (failedLoginCounter != null) {
            FailedLoginRecorder recorder = new FailedLoginRecorder();
            failedLoginHandlers.add(recorder);
            okLoginHandlers.add(recorder);
        }
    }

    /**
     * 允许无角色用户登录
     * 
     * @return
     */
    public LoginContext allowWithoutRole() {
        this.allowWithoutRole = true;
        return this;
    }

    /**
     * 不隐藏账号不存在的异常信息
     * 
     * @return
     */
    public LoginContext dontHideAccountNotFoundException() {
        this.hideAccountNotFoundException = false;
        return this;
    }

    /**
     * 添加登录异常处理器
     * 
     * @param handlers
     * @return
     */
    public LoginContext failedHandler(FailedLoginHandler... handlers) {
        Collections.addAll(failedLoginHandlers, handlers);
        return this;
    }

    /**
     * 添加登录成功处理器
     * 
     * @param handlers
     * @return
     */
    public LoginContext okHandler(OkLoginHandler... handlers) {
        Collections.addAll(okLoginHandlers, handlers);
        return this;
    }

    protected LoginUser createLoginUser(SimpleAuthcInfo user, Set<? extends SimpleRole> userRoles) {
        SimpleLoginUser loginUser = new SimpleLoginUser(user.getId(), user.stringPrincipal());
        loginUser.addRoles(userRoles);
        return loginUser;
    }

    protected class FailedLoginRecorder implements FailedLoginHandler, OkLoginHandler {

        @Override
        public void onFailure(AuthToken token, AuthcException e) {
            // 密码错误增加认证失败次数
            if (e.isError(AuthcError.Credential) && failedLoginCounter != null) {
                failedLoginCounter.incrementAndCheck(token.stringPrincipal());
            }
        }

        @Override
        public void onSuccessful(LoginUser loginUser) {
            // 登录成功一次删除失败次数
            if (failedLoginCounter != null) {
                failedLoginCounter.clear(loginUser.getUserName());
            }
        }

    }
}
